/**
 * DSM - Distributed Search Manager
 * Developed by Milspec Research International Pty Ltd
 * $Author: gsingh $
 * $Revision: 1.3 $
 * $Date: 2006/05/04 02:06:35 $
 * (c)Copyright 2004
 * education.au limited
 * DEST
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the names education.au limited, DEST nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 * To the extent permitted by law, the copyright owners of this software and
 * its contributors:
 * (i) exclude all warranties in relation to the software; and
 * (ii) exclude liability for all losses, costs, expenses and damages arising
 * in any way from the use of the software whether arising from or in
 * relation to breach of contract, negligence or any other tort, in equity
 * or otherwise. If the software is in breach of a warranty which is
 * implied by law, the copyright owners and contributors liability is
 * limited to the replacement of the software.
 *
 * @author	gsingh
 */

package au.edu.educationau.opensource.dsm.util;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Standalone URI Encoder class
 */

public class URIEncoder {

	/**
	 * A list of invalid characters that can't exist within filenames. If they
	 * appear then the DiskCache will escape them. The current list is in part
	 * based on Microsoft Knowledge Base article Q177506 (because DOS
	 * filesystems are more generally limited than UNIX filesystems).
	 * 
	 * SGP: Windows NT refuses to take "?", so I add it to the list.
	 * Additionally, if we encode "?", the jdk runtime logic decodes it twice
	 * for "file:" urls, giving a filename with a space in it. I have fixed it
	 * in JetspeedDiskCacheEntry.java, avoiding the creation of a new URL when
	 * getFile() is not null.
	 */
	public static final String[] INVALID_CHARACTERS = { "\\", "/", ":", "*", "\"", "<", ">", "|", "+", "?" };
	/***/
	public static final String[] CODED_CHARACTERS = { "#" + (int) '\\' + ";", "#" + (int) '/' + ";", "#" + (int) ':' + ";", "#" + (int) '*' + ";", "#" + (int) '"' + ";", "#" + (int) '<' + ";", "#" + (int) '>' + ";", "#" + (int) '|' + ";", "#" + (int) '+' + ";", "#" + (int) '?' + ";" };

	/**
	 * Encode the given URI
	 * 
	 * @param uri
	 */
	public static String encode(String uri) {

		if (uri == null) {
			throw new IllegalArgumentException("URI may not be null. ");
		}

		/*
		 * 
		 * Very basic encoding... should work for most circumstances.
		 * 
		 * files like http://www.apache.org:80/index.html
		 * 
		 * will be changed to:
		 * 
		 * http_www.apache.org___80.index.html
		 *  - a "_" -> "__" - a "://" -> "_" - a "/" -> "_" - a ":" -> "___"
		 */

		StringBuffer buffer = new StringBuffer(uri);
		StringUtils.replaceAll(buffer, "_", "__");
		StringUtils.replaceAll(buffer, "://", "_");
		StringUtils.replaceAll(buffer, "/", "_");
		StringUtils.replaceAll(buffer, ":", "___");

		// if there are any characters that can't be stored in a filesystem
		// encode
		// them now
		encodeQueryData(buffer);

		return buffer.toString();
	}

	/**
	 * Hash the given URL
	 * 
	 * @param url
	 */
	public static String hashURL(String url) {
		if (url == null) {
			throw new IllegalArgumentException("URL may not be null. ");
		}

		String result = null;

		try {
			MessageDigest md = MessageDigest.getInstance("SHA-1");
			if (md != null) {
				md.reset();
				md.update(url.getBytes());
				BigInteger hash = new BigInteger(1, md.digest());
				result = hash.toString(16);
			}
			md = null;
		}

		catch (NoSuchAlgorithmException ex) {
			result = null;
		}

		return result;
	}

	/**
	 * Decode the given URI.
	 * 
	 * @param uri
	 */
	public static String decode(String uri) {

		if (uri == null) {
			throw new IllegalArgumentException("URI may not be null. ");
		}

		String newURI = "";

		int start = uri.indexOf("_");

		String protocol = null;

		// SGP: needed if uri does not contain protocol but contains "_"
		if (uri.charAt(start + 1) == '_') {
			start = -1;
		}

		if (start > -1) {
			protocol = uri.substring(0, start);
		}

		newURI = uri.substring(start + 1, uri.length());
		StringBuffer buffer = new StringBuffer(newURI);

		StringUtils.replaceAll(buffer, "___", ":");

		StringUtils.replaceAll(buffer, "_", "/");
		StringUtils.replaceAll(buffer, "_", "/");

		// now the original "__" should be slashes so replace them with a single
		// "_"
		StringUtils.replaceAll(buffer, "//", "_");

		if (protocol != null) {
			buffer.replace(0, 0, "://"); // prepend string
			buffer.replace(0, 0, protocol); // prepend protocol
		}

		decodeQueryData(buffer);

		return buffer.toString();
	}

	/**
	 * <p>
	 * If this data contains any INVALID_CHARACTERS encode the data into a
	 * target String.
	 * </p>
	 * 
	 * <p>
	 * NOTE: the algorithm between encode and decode is shared, if you modify
	 * one you should modify the other.
	 * </p>
	 * 
	 * @see decode(String data)
	 */
	private static StringBuffer encodeQueryData(StringBuffer data) {

		for (int i = 0; i < INVALID_CHARACTERS.length; ++i) {

			String source = INVALID_CHARACTERS[i];

			String coded = CODED_CHARACTERS[i];

			data = StringUtils.replaceAll(data, source, coded);

		}

		return data;
	}

	/**
	 * <p>
	 * If this data contains any encoded INVALID_CHARACTERS, decode the data
	 * back into the source string
	 * </p>
	 * 
	 * <p>
	 * NOTE: the algorithm between encode and decode is shared, if you modify
	 * one you should modify the other.
	 * </p>
	 * 
	 * @see encode(String data)
	 */
	private static StringBuffer decodeQueryData(StringBuffer data) {

		for (int i = 0; i < INVALID_CHARACTERS.length; ++i) {

			String source = INVALID_CHARACTERS[i];

			String coded = CODED_CHARACTERS[i];

			data = StringUtils.replaceAll(data, coded, source);

		}

		return data;
	}

}
